package com.astoolkit.commons.factory
{
	import flash.utils.getQualifiedClassName;
	import flash.utils.setTimeout;
	
	import mx.core.ClassFactory;
	import mx.core.IFactory;
	import mx.logging.ILogger;
	import mx.logging.Log;
	import mx.utils.ArrayUtil;
	import mx.utils.UIDUtil;
	
	public class PooledFactory implements IPooledFactory
	{
		private static const LOGGER : ILogger = 
			Log.getLogger( getQualifiedClassName( PooledFactory ).replace(/:+/g, "." ) );

		private var _pool : Array = [];
		private var _busyObjects : Array = [];
		
		public var defaultType : Class;
		public var defaultProperties : Object;
		
		public var poolCleanupDelay : int = 5000;
		public var minimumStock : int = 1;
		
		private var _delegate : IPooledFactoryDelegate;
		
		private var _backupProperties : Array;
		
		private var _propertiesBackup : Object = {};
		
		private var _genericFactory : ClassFactory = new ClassFactory();
		
		public function set delegate( inDelegate : IPooledFactoryDelegate ) : void
		{
			_delegate = inDelegate;
		}
		
		public function set backupProperties(inProperties : Array ) : void
		{
			_backupProperties = inProperties;
		}

		/**
		 * creates a new instance of the give type or of <code>defaultType</code> class
		 * or returns a pooled instance
		 */
		public function getInstance( inType : Class = null, inProperties : Object = null ) : *
		{
			if( !inType )
				inType = defaultType;
			if( !inProperties )
				inProperties = defaultProperties;
			var out : Object;
			if( _delegate && _delegate.delegateInstantiation )
				out = _delegate.newInstance( inType, inProperties );
			else
			{
				_genericFactory.properties = inProperties;
				_genericFactory.generator = inType;
				out = _genericFactory.newInstance();
			}
			_pool.push( out );
			if( _backupProperties && _backupProperties.length > 0 )
			{
				var backup : Object = 
					_propertiesBackup.hasOwnProperty( UIDUtil.getUID( out ) );
				if( !backup )
				{
					backup = {};
					_propertiesBackup[ UIDUtil.getUID( out ) ] = backup;
				}
				for each( var p : String in _backupProperties ) 
				{
					if( out.hasOwnProperty( p ) )
						backup[ p ] = out[ p ];
					else
						LOGGER.warn( "{0} doesn't have a '{1}' property", getQualifiedClassName( out ), p );
				}
			}
			return out;
		}
		
		public function hasPooledInstance( inType : Object ) : Boolean
		{
			return false;
		}
		
		/**
		 * returns an object to the pool
		 */
		public function release( inObject : Object ) : void
		{
			_busyObjects.splice( ArrayUtil.getItemIndex( inObject, _busyObjects ), 1 );
			_pool.push( inObject );
			if( _backupProperties && _backupProperties.length > 0 )
			{
				var backup : Object = 
					_propertiesBackup[ UIDUtil.getUID( inObject ) ];
				for each( var p : String in _backupProperties ) 
				{
					if( inObject.hasOwnProperty( p ) )
						inObject[ p ] = backup[ p ];
				}
			}
			if( _delegate )
				_delegate.onRelease( inObject );
			if( _busyObjects.length == 0 )
				setTimeout( vacuum, poolCleanupDelay );
		}
		
		/**
		 * cleans up the pool retaining only <code>minimumStock</code> instances
		 */
		public function vacuum( inType : Object = null ) : void
		{
			if( _busyObjects.length == 0 )
			{
				while( _pool.length > minimumStock )
				{
					var object : Object = _pool.pop();
					if( _propertiesBackup.hasOwnProperty( UIDUtil.getUID( object ) ) )
						delete _propertiesBackup[ UIDUtil.getUID( object ) ];
					if( _delegate )
						_delegate.onDestroy( object );
				}
			}

		}
		
		
		public function newInstance():*
		{
			if( defaultType )
				return getInstance( defaultType, defaultProperties );
			LOGGER.warn( "PooledFactory newInstance() is returning an instance of Object " +
				"because defaultType wasn't set" );
			return {};
		}
		
	}
}